﻿/// 迟迟 2224273481@qq.com
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace YouYou_Protobuf
{
    class Program
    {
        private static string _OutPath;
        private static StringBuilder _SbProtoIdDefine;
        private static int _ClassIndex;
        private static int _ProtoIndex;

        static void Main(string[] args)
        {
            _SbProtoIdDefine = new StringBuilder();
            _SbProtoIdDefine.Append(@"/// YouYou 迟迟
/// <summary>
/// 协议编号
/// </summary>
public class ProtoIdDefine
{
");
            string settingpath = $"{Directory.GetCurrentDirectory()}/Setting.txt";
            Console.WriteLine(settingpath);
            if (!File.Exists(settingpath))
            {
                Console.WriteLine("配置文件不存在!!!!");
                Console.ReadLine();
                return;
            }
            string[] settingArr = File.ReadAllLines(settingpath);
            if (settingArr.Length < 1)
            {
                Console.WriteLine("配置文件格式错误!!!!");
                Console.ReadLine();
                return;
            }

            _OutPath = $"{Directory.GetCurrentDirectory()}/Out".Replace('\\','/');
            if (!Directory.Exists(_OutPath))
            {
                Directory.CreateDirectory(_OutPath);
            }

            string protoPath = $"{Directory.GetCurrentDirectory()}/Proto";
            if (!Directory.Exists(protoPath))
            {
                Console.WriteLine("Proto文件夹不存在!");
                Directory.CreateDirectory(protoPath);
                return;
            }
            string[] pathArr = Directory.GetFiles(protoPath, "*.proto");
            if (pathArr.Length == 0)
            {
                Console.WriteLine("Proto文件不存在!");
                return;
            }
            Console.WriteLine("开始生成!");
            for (int i = 0; i < pathArr.Length; i++)
            {
                string[] parr = pathArr[i].Replace('\\', '/').Split('/');
                AddProto(File.ReadAllLines(pathArr[i], Encoding.UTF8), parr[parr.Length-1]);
            }

            _SbProtoIdDefine.AppendLine("}");
            File.WriteAllText(_OutPath + "/ProtoIdDefine.cs", _SbProtoIdDefine.ToString(), Encoding.UTF8);

            #region 复制到配置文件夹
            string[] arr = Directory.GetFiles(_OutPath, "*.cs");
            string npathd = settingArr[0].Replace('\\', '/');
            if (!Directory.Exists(npathd))
            {
                Directory.CreateDirectory(npathd);
            }
            for (int i = 0; i < arr.Length; i++)
            {
                string oldpath = arr[i].Replace('\\', '/');
                string newpath = oldpath.Replace(_OutPath, npathd);
                Console.WriteLine(newpath);
                File.Copy(oldpath, newpath, true);
            }
            Console.WriteLine("全部生成完毕");
            #endregion

            Console.ReadKey();
        }


        public static void AddProto(string[] msgArr,string name)
        {
            try
            {
                _ClassIndex = 0;
                _ProtoIndex++;
                string namesp = "Proto";
                int startindex = 0;

                int len = msgArr.Length;
                for (int i = 0; i < len; i++)
                {
                    if (msgArr[i].IndexOf("package ") == 0)
                    {
                        namesp = msgArr[i].Trim().Split(';')[0].Split(' ')[1];
                        startindex = i;
                        break;
                    }
                }


                StringBuilder sb = new StringBuilder();
                sb.AppendLine("// <auto-generated>")
                    .AppendLine("// 该代码为自动生成请不要随意更改")
                    .AppendLine("// 要改的话用拓展类")
                    .AppendLine("// 格式文件为: " + name)
                    .AppendLine("// </auto-generated>\n\n")
                    .AppendLine("#region Designer generated code")
                    .AppendLine("#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192")
                    .AppendLine("namespace " + namesp)
                    .AppendLine("{");

                List<string> list = new List<string>();
                int ycount = 0;
                string classname = "";
                string tips = "";
                byte classtype = 0x00;
                for (int i = startindex; i < len; i++)
                {
                    if (ycount == 0)
                    {
                        if (msgArr[i].Trim().IndexOf("message ") == 0|| msgArr[i].Trim().IndexOf("enum ") == 0)
                        {
                            if (msgArr[i].Trim().IndexOf("message ") == 0)
                            {
                                classtype = 1;
                            }
                            else
                            {
                                classtype = 2;
                            }
                            list.Clear();
                            list.Add("{");
                            if (i - 1 >= 0 && msgArr[i - 1].Trim().IndexOf("//") == 0)
                            {
                                tips = msgArr[i - 1].Trim().Substring(2);
                            }
                            else
                            {
                                tips = "";
                            }
                            classname = msgArr[i].Trim().Split(' ')[1];
                            i++;
                            ycount++;

                        }
                    }
                    else
                    {
                        if (msgArr[i].Trim().IndexOf("{") == 0)
                        {
                            ycount++;
                            list.Add("{");
                        }
                        else if (msgArr[i].Trim().IndexOf("}") == 0)
                        {
                            list.Add("}");

                            ycount--;
                            if (ycount == 0)
                            {
                                if (classtype == 0x01)
                                {
                                    AddProtoClass(sb, tips, classname, list, "");
                                }
                                else
                                {
                                    AddEnum(sb, tips, classname, list, "");
                                }
                            }
                        }
                        else
                        {
                            list.Add(msgArr[i].Trim());
                        }
                    }



                }

                sb.AppendLine("}")
                    .AppendLine("#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192")
                    .AppendLine("#endregion");

                File.WriteAllText(_OutPath + "/" + name.Replace(".proto",".cs"), sb.ToString(), Encoding.UTF8);
                Console.WriteLine("生成成功:" + name);
            }
            catch (Exception ex)
            {
                Console.WriteLine("生成失败:" + name);

            }

        }


        private static void AddEnum(StringBuilder sb, string tips, string classname, List<string> list, string tustr)
        {
            string newclassname = classname.Replace("_", "");

            if (tips != "")
            {
                sb.AppendLine(tustr + "    /// <summary>")
                    .AppendLine(tustr + "    /// " + tips)
                    .AppendLine(tustr + "    /// </summary>");
            }

            string mstips = newclassname != classname ? $"Name = @\"{classname}\"" : "";
            sb.AppendLine(tustr + $"     [global::ProtoBuf.ProtoContract({mstips})]");
            sb.Append(tustr + "    public enum ").Append(newclassname).Append("\n")
                .AppendLine(tustr + "    {");

            for (int i = 0; i < list.Count; i++)
            {
                if (list[i].IndexOf(";")>0)
                {
                    string[] estrarr = list[i].Split(';')[0].Trim().Split('=');
                    sb.AppendLine(tustr + $"        {estrarr[0].Trim()} = {estrarr[1].Trim()},");
                }

            }
            sb.AppendLine(tustr + "    }");
        }

        private static void AddProtoClass(StringBuilder sb,string tips,string classname, List<string> list, string tustr)
        {

            string newclassname = classname.Replace("_", "");
            _ClassIndex++;
            ushort protoId = (ushort)(_ClassIndex + (_ProtoIndex * 1000) + 9000);
            if (tips!= null)
            {
                _SbProtoIdDefine.AppendLine("    /// <summary>")
                    .Append("    /// ").Append(tips).Append("\n")
                    .AppendLine("    /// </summary>");
            }
            _SbProtoIdDefine.AppendLine($"　　public const ushort Proto_{newclassname} = {protoId};\n");

            if (tips != "")
            {
                sb.AppendLine(tustr + "    /// <summary>")
                    .AppendLine(tustr + $"    /// " + tips)
                    .AppendLine(tustr + "    /// </summary>");
            }
            string mstips = newclassname != classname ? $"Name = @\"{classname}\"" : "";

            sb.AppendLine(tustr + $"    [global::ProtoBuf.ProtoContract({mstips})]")
                .AppendLine(tustr + $"    public partial class {newclassname} : IProto,global::ProtoBuf.IExtensible")
                .AppendLine(tustr + "    {")
                .AppendLine(tustr + "        private global::ProtoBuf.IExtension __pbn__extensionData;")
                .AppendLine(tustr + "        global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)")
                .AppendLine(tustr + "            => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);\n");
            #region 序列化反序列化和ProtoId
            sb.AppendLine(tustr + $"        public static {newclassname} Create(System.IO.MemoryStream ms)")
                .AppendLine(tustr + "        {")
                .AppendLine(tustr + $"        　　return ProtoBuf.Serializer.Deserialize<{newclassname}>(ms);")
                .AppendLine(tustr + "        }\n");

            sb.AppendLine(tustr + $"        public byte[] ToArray(System.IO.MemoryStream ms)")
                .AppendLine(tustr + "        {")
                .AppendLine(tustr + "        　　ms.SetLength(0);")
                .AppendLine(tustr + $"        　　ProtoBuf.Serializer.Serialize<{newclassname}>(ms, this);")
                .AppendLine(tustr + "        　　return ms.ToArray();")
                .AppendLine(tustr + "        }\n");

            sb.AppendLine(tustr + $"        public ushort ProtoId => ProtoIdDefine.Proto_{newclassname};\n");
            #endregion

            int nycount = 0;
            string nclassname = "";
            string ntips = "";
            List<string> nlist = new List<string>();
            int count = list.Count;
            for (int i = 0; i < count; i++)
            {
                if (nycount == 0)
                {
                    if (list[i].Trim().IndexOf("message ") == 0)
                    {
                        i++;
                        nycount++;
                        nlist.Clear();
                        if (i - 1 >= 0 && list[i - 1].IndexOf("//") == 0)
                        {
                            ntips = list[i - 1].Substring(2);
                        }
                        else
                        {
                            ntips = "";
                        }
                        nlist.Add("{");
                        nclassname = list[i-1].Split(' ')[1];
                    }
                    else
                    {
                        if (list[i].IndexOf(";") > 0)
                        {
                            AddProperty(sb, list[i], tustr+"    ");
                        }
                    }
                }
                else
                {
                    if (list[i].IndexOf("{") == 0)
                    {
                        nycount++;
                        nlist.Add("{");

                    }
                    else if (list[i].IndexOf("}") == 0)
                    {
                        nlist.Add("}");

                        nycount--;
                        if (nycount == 0)
                        {
                            AddProtoClass(sb, ntips, nclassname, nlist, tustr + "    ");
                        }
                    }
                    else
                    {
                        nlist.Add(list[i]);
                    }
                }

            }


            sb.AppendLine(tustr + "    }");

        }
        private static void AddProperty(StringBuilder sb,string msg, string tustr)
        {
            msg = msg.Trim();
            string[] arr = msg.Split(';');
            string beizhu = arr[1].TrimStart();
            if (beizhu.IndexOf("//") == 0)
            {
                sb.AppendLine(tustr + "    /// <summary>")
                    .AppendLine(tustr + "    /// " + beizhu.Substring(2))
                    .AppendLine(tustr + "    /// </summary>");
            }

            string[] ssarr = arr[0].Split('=');
            string[] scs = ssarr[0].Split(' ');
            string classname = scs[0].Trim();
            if (classname == "repeated")
            {
                scs = ssarr[0].Replace("repeated ","").Split(' ');
            }

            string propertyname = scs[1].Trim();
            int index = int.Parse(ssarr[ssarr.Length-1]);

            classname = ConvertType(classname);

            string npset = propertyname.Substring(0, 1).ToUpper() + (propertyname.Length > 1? propertyname.Substring(1):"");
            if (npset != propertyname || classname == "repeated")
            {
                if (classname != "repeated")
                {
                    sb.AppendLine(tustr + $"    [global::ProtoBuf.ProtoMember({index}, Name = @\"{propertyname}\")]");
                }
            }
            else
            {
                sb.AppendLine(tustr + $"    [global::ProtoBuf.ProtoMember({index})]");
            }

            if (classname == "string")
            {
                sb.AppendLine(tustr + "    [global::System.ComponentModel.DefaultValue(\"\")]")
                    .AppendLine(tustr + $"    public string "+ npset + "{ get; set; } = \"\";");
                
            }
            else if(classname == "repeated")
            {
                classname = scs[0].Replace("_", "");
                classname = ConvertType(classname);
                if (classname == "int" || classname == "float" || classname == "double" || classname == "long" ||
                    classname == "uint" || classname == "ulong" || classname == "ushort" || classname == "byte[]")
                {
                    sb.AppendLine(tustr + $"    [global::ProtoBuf.ProtoMember({index}, Name = @\"{scs[1]}\" , IsPacked = true)]");
                    sb.Append(tustr + $"    public {classname}[] {scs[1]} ").Append("{ get; set; }\n");
                }
                else
                {
                    sb.AppendLine(tustr + $"    [global::ProtoBuf.ProtoMember({index}, Name = @\"{scs[1]}\")]");
                    sb.Append(tustr + $"    public global::System.Collections.Generic.List<{classname}> {scs[1]} ")
                        .Append("{ get; } = new global::System.Collections.Generic.List").Append($"<{classname}>();\n");
                }

            }
            else
            {
                sb.AppendLine(tustr + $"    public {classname.Replace("_", "")} {scs[1]} " + "{ get; set; }\n");
            }
        }

        private static string ConvertType(string type)
        {
            string typeCs = "";
            switch (type)
            {
                case "int16":
                    typeCs = "short";
                    break;
                case "int32":
                    typeCs = "int";
                    break;
                case "bytes":
                    typeCs = "byte[]";
                    break;
                case "uint32":
                    typeCs = "uint";
                    break;
                case "long":
                    typeCs = "long";
                    break;
                case "int64":
                    typeCs = "long";
                    break;
                case "uint64":
                    typeCs = "ulong";
                    break;
                case "uint16":
                    typeCs = "ushort";
                    break;
                default:
                    typeCs = type;
                    break;
            }

            return typeCs;
        }
    }
}
